"""
滑点模型

提供各种滑点计算模型，用于模拟交易执行时的价格滑点。
"""

from abc import ABC, abstractmethod
from enum import Enum
from typing import Dict, List, Optional, Any
from datetime import datetime, timedelta
from dataclasses import dataclass
import math
from decimal import Decimal

from .order_manager import Order, OrderSide, OrderType


class SlippageType(Enum):
    """滑点类型枚举"""
    FIXED = "fixed"  # 固定滑点
    LINEAR = "linear"  # 线性滑点
    SQUARE_ROOT = "square_root"  # 平方根滑点
    LOGARITHMIC = "logarithmic"  # 对数滑点
    MARKET_IMPACT = "market_impact"  # 市场冲击滑点


@dataclass
class SlippageParams:
    """滑点参数"""
    base_slippage: float = 0.0001  # 基础滑点（基点）
    volume_factor: float = 0.1  # 成交量因子
    volatility_factor: float = 0.05  # 波动率因子
    liquidity_factor: float = 1.0  # 流动性因子
    time_factor: float = 0.01  # 时间因子
    market_impact_factor: float = 0.5  # 市场冲击因子
    min_slippage: float = 0.0  # 最小滑点
    max_slippage: float = 0.01  # 最大滑点（1%）


@dataclass
class MarketData:
    """市场数据"""
    symbol: str  # 标的代码
    price: Decimal  # 当前价格
    bid_price: Optional[Decimal] = None  # 买一价
    ask_price: Optional[Decimal] = None  # 卖一价
    volume: Optional[Decimal] = None  # 成交量
    avg_volume: Optional[Decimal] = None  # 平均成交量
    volatility: Optional[float] = None  # 波动率
    spread: Optional[Decimal] = None  # 买卖价差
    market_cap: Optional[Decimal] = None  # 市值
    timestamp: Optional[datetime] = None


@dataclass
class SlippageResult:
    """滑点结果"""
    original_price: Decimal  # 原始价格
    slipped_price: Decimal  # 滑点后价格
    slippage_amount: Decimal  # 滑点金额
    slippage_rate: float  # 滑点率
    slippage_cost: Decimal  # 滑点成本
    model_type: SlippageType  # 滑点模型类型
    factors: Dict[str, float]  # 影响因子


class BaseSlippageModel(ABC):
    """基础滑点模型抽象类"""
    
    def __init__(self, params: SlippageParams):
        """
        初始化滑点模型
        
        Args:
            params: 滑点参数
        """
        self.params = params
    
    @abstractmethod
    def calculate_slippage(
        self,
        order: Order,
        market_data: MarketData,
        execution_time: Optional[datetime] = None
    ) -> SlippageResult:
        """
        计算滑点
        
        Args:
            order: 订单信息
            market_data: 市场数据
            execution_time: 执行时间
            
        Returns:
            SlippageResult: 滑点结果
        """
        pass
    
    def _apply_constraints(self, slippage_rate: float) -> float:
        """应用滑点约束"""
        return max(
            self.params.min_slippage,
            min(self.params.max_slippage, slippage_rate)
        )
    
    def _calculate_volume_impact(self, order_quantity: Decimal, avg_volume: Optional[Decimal]) -> float:
        """计算成交量冲击"""
        if not avg_volume or avg_volume <= 0:
            return 0.0
        
        volume_ratio = float(order_quantity / avg_volume)
        return self.params.volume_factor * volume_ratio
    
    def _calculate_volatility_impact(self, volatility: Optional[float]) -> float:
        """计算波动率冲击"""
        if not volatility:
            return 0.0
        
        return self.params.volatility_factor * volatility
    
    def _calculate_liquidity_impact(self, market_data: MarketData) -> float:
        """计算流动性冲击"""
        if not market_data.spread or not market_data.price:
            return 0.0
        
        spread_ratio = float(market_data.spread / market_data.price)
        return self.params.liquidity_factor * spread_ratio
    
    def _calculate_time_impact(self, order: Order, execution_time: Optional[datetime]) -> float:
        """计算时间冲击"""
        if not execution_time:
            return 0.0
        
        time_diff = (execution_time - order.created_at).total_seconds()
        time_hours = time_diff / 3600.0
        
        return self.params.time_factor * math.sqrt(time_hours)


class FixedSlippageModel(BaseSlippageModel):
    """固定滑点模型"""
    
    def calculate_slippage(
        self,
        order: Order,
        market_data: MarketData,
        execution_time: Optional[datetime] = None
    ) -> SlippageResult:
        """计算固定滑点"""
        slippage_rate = self.params.base_slippage
        slippage_rate = self._apply_constraints(slippage_rate)
        
        # 根据买卖方向调整滑点
        if order.side == OrderSide.BUY:
            slipped_price = market_data.price * (1 + Decimal(str(slippage_rate)))
        else:
            slipped_price = market_data.price * (1 - Decimal(str(slippage_rate)))
        
        slippage_amount = abs(slipped_price - market_data.price)
        slippage_cost = slippage_amount * order.quantity
        
        return SlippageResult(
            original_price=market_data.price,
            slipped_price=slipped_price,
            slippage_amount=slippage_amount,
            slippage_rate=slippage_rate,
            slippage_cost=slippage_cost,
            model_type=SlippageType.FIXED,
            factors={"base_slippage": slippage_rate}
        )


class LinearSlippageModel(BaseSlippageModel):
    """线性滑点模型"""
    
    def calculate_slippage(
        self,
        order: Order,
        market_data: MarketData,
        execution_time: Optional[datetime] = None
    ) -> SlippageResult:
        """计算线性滑点"""
        # 基础滑点
        slippage_rate = self.params.base_slippage
        
        # 成交量冲击
        volume_impact = self._calculate_volume_impact(order.quantity, market_data.avg_volume)
        
        # 波动率冲击
        volatility_impact = self._calculate_volatility_impact(market_data.volatility)
        
        # 流动性冲击
        liquidity_impact = self._calculate_liquidity_impact(market_data)
        
        # 时间冲击
        time_impact = self._calculate_time_impact(order, execution_time)
        
        # 线性组合
        total_slippage = slippage_rate + volume_impact + volatility_impact + liquidity_impact + time_impact
        total_slippage = self._apply_constraints(total_slippage)
        
        # 根据买卖方向调整滑点
        if order.side == OrderSide.BUY:
            slipped_price = market_data.price * (1 + Decimal(str(total_slippage)))
        else:
            slipped_price = market_data.price * (1 - Decimal(str(total_slippage)))
        
        slippage_amount = abs(slipped_price - market_data.price)
        slippage_cost = slippage_amount * order.quantity
        
        return SlippageResult(
            original_price=market_data.price,
            slipped_price=slipped_price,
            slippage_amount=slippage_amount,
            slippage_rate=total_slippage,
            slippage_cost=slippage_cost,
            model_type=SlippageType.LINEAR,
            factors={
                "base_slippage": slippage_rate,
                "volume_impact": volume_impact,
                "volatility_impact": volatility_impact,
                "liquidity_impact": liquidity_impact,
                "time_impact": time_impact
            }
        )


class SquareRootSlippageModel(BaseSlippageModel):
    """平方根滑点模型"""
    
    def calculate_slippage(
        self,
        order: Order,
        market_data: MarketData,
        execution_time: Optional[datetime] = None
    ) -> SlippageResult:
        """计算平方根滑点"""
        # 基础滑点
        slippage_rate = self.params.base_slippage
        
        # 成交量冲击（平方根关系）
        volume_impact = 0.0
        if market_data.avg_volume and market_data.avg_volume > 0:
            volume_ratio = float(order.quantity / market_data.avg_volume)
            volume_impact = self.params.volume_factor * math.sqrt(volume_ratio)
        
        # 波动率冲击
        volatility_impact = self._calculate_volatility_impact(market_data.volatility)
        
        # 流动性冲击
        liquidity_impact = self._calculate_liquidity_impact(market_data)
        
        # 时间冲击（已经是平方根关系）
        time_impact = self._calculate_time_impact(order, execution_time)
        
        # 市场冲击（平方根关系）
        market_impact = 0.0
        if market_data.market_cap and market_data.market_cap > 0:
            order_value = order.quantity * market_data.price
            impact_ratio = float(order_value / market_data.market_cap)
            market_impact = self.params.market_impact_factor * math.sqrt(impact_ratio)
        
        # 组合滑点
        total_slippage = slippage_rate + volume_impact + volatility_impact + liquidity_impact + time_impact + market_impact
        total_slippage = self._apply_constraints(total_slippage)
        
        # 根据买卖方向调整滑点
        if order.side == OrderSide.BUY:
            slipped_price = market_data.price * (1 + Decimal(str(total_slippage)))
        else:
            slipped_price = market_data.price * (1 - Decimal(str(total_slippage)))
        
        slippage_amount = abs(slipped_price - market_data.price)
        slippage_cost = slippage_amount * order.quantity
        
        return SlippageResult(
            original_price=market_data.price,
            slipped_price=slipped_price,
            slippage_amount=slippage_amount,
            slippage_rate=total_slippage,
            slippage_cost=slippage_cost,
            model_type=SlippageType.SQUARE_ROOT,
            factors={
                "base_slippage": slippage_rate,
                "volume_impact": volume_impact,
                "volatility_impact": volatility_impact,
                "liquidity_impact": liquidity_impact,
                "time_impact": time_impact,
                "market_impact": market_impact
            }
        )


class LogarithmicSlippageModel(BaseSlippageModel):
    """对数滑点模型"""
    
    def calculate_slippage(
        self,
        order: Order,
        market_data: MarketData,
        execution_time: Optional[datetime] = None
    ) -> SlippageResult:
        """计算对数滑点"""
        # 基础滑点
        slippage_rate = self.params.base_slippage
        
        # 成交量冲击（对数关系）
        volume_impact = 0.0
        if market_data.avg_volume and market_data.avg_volume > 0:
            volume_ratio = float(order.quantity / market_data.avg_volume)
            if volume_ratio > 0:
                volume_impact = self.params.volume_factor * math.log(1 + volume_ratio)
        
        # 波动率冲击
        volatility_impact = self._calculate_volatility_impact(market_data.volatility)
        
        # 流动性冲击
        liquidity_impact = self._calculate_liquidity_impact(market_data)
        
        # 时间冲击
        time_impact = self._calculate_time_impact(order, execution_time)
        
        # 组合滑点
        total_slippage = slippage_rate + volume_impact + volatility_impact + liquidity_impact + time_impact
        total_slippage = self._apply_constraints(total_slippage)
        
        # 根据买卖方向调整滑点
        if order.side == OrderSide.BUY:
            slipped_price = market_data.price * (1 + Decimal(str(total_slippage)))
        else:
            slipped_price = market_data.price * (1 - Decimal(str(total_slippage)))
        
        slippage_amount = abs(slipped_price - market_data.price)
        slippage_cost = slippage_amount * order.quantity
        
        return SlippageResult(
            original_price=market_data.price,
            slipped_price=slipped_price,
            slippage_amount=slippage_amount,
            slippage_rate=total_slippage,
            slippage_cost=slippage_cost,
            model_type=SlippageType.LOGARITHMIC,
            factors={
                "base_slippage": slippage_rate,
                "volume_impact": volume_impact,
                "volatility_impact": volatility_impact,
                "liquidity_impact": liquidity_impact,
                "time_impact": time_impact
            }
        )


class MarketImpactSlippageModel(BaseSlippageModel):
    """市场冲击滑点模型"""
    
    def calculate_slippage(
        self,
        order: Order,
        market_data: MarketData,
        execution_time: Optional[datetime] = None
    ) -> SlippageResult:
        """计算市场冲击滑点"""
        # 基础滑点
        slippage_rate = self.params.base_slippage
        
        # 临时冲击（立即影响）
        temporary_impact = 0.0
        if market_data.avg_volume and market_data.avg_volume > 0:
            participation_rate = float(order.quantity / market_data.avg_volume)
            temporary_impact = self.params.market_impact_factor * math.sqrt(participation_rate)
        
        # 永久冲击（持续影响）
        permanent_impact = 0.0
        if market_data.market_cap and market_data.market_cap > 0:
            order_value = order.quantity * market_data.price
            impact_ratio = float(order_value / market_data.market_cap)
            permanent_impact = self.params.market_impact_factor * 0.5 * impact_ratio
        
        # 波动率调整
        volatility_adjustment = 1.0
        if market_data.volatility:
            volatility_adjustment = 1.0 + market_data.volatility
        
        # 流动性调整
        liquidity_adjustment = 1.0
        if market_data.spread and market_data.price:
            spread_ratio = float(market_data.spread / market_data.price)
            liquidity_adjustment = 1.0 + spread_ratio * 10  # 放大流动性影响
        
        # 总冲击
        total_impact = (temporary_impact + permanent_impact) * volatility_adjustment * liquidity_adjustment
        total_slippage = slippage_rate + total_impact
        total_slippage = self._apply_constraints(total_slippage)
        
        # 根据买卖方向调整滑点
        if order.side == OrderSide.BUY:
            slipped_price = market_data.price * (1 + Decimal(str(total_slippage)))
        else:
            slipped_price = market_data.price * (1 - Decimal(str(total_slippage)))
        
        slippage_amount = abs(slipped_price - market_data.price)
        slippage_cost = slippage_amount * order.quantity
        
        return SlippageResult(
            original_price=market_data.price,
            slipped_price=slipped_price,
            slippage_amount=slippage_amount,
            slippage_rate=total_slippage,
            slippage_cost=slippage_cost,
            model_type=SlippageType.MARKET_IMPACT,
            factors={
                "base_slippage": slippage_rate,
                "temporary_impact": temporary_impact,
                "permanent_impact": permanent_impact,
                "volatility_adjustment": volatility_adjustment,
                "liquidity_adjustment": liquidity_adjustment
            }
        )


class SlippageModel:
    """
    滑点模型管理器
    
    管理和协调各种滑点模型。
    """
    
    def __init__(self, default_params: Optional[SlippageParams] = None):
        """
        初始化滑点模型管理器
        
        Args:
            default_params: 默认滑点参数
        """
        self.default_params = default_params or SlippageParams()
        self._models: Dict[SlippageType, BaseSlippageModel] = {}
        self._register_default_models()
    
    def _register_default_models(self) -> None:
        """注册默认滑点模型"""
        self._models[SlippageType.FIXED] = FixedSlippageModel(self.default_params)
        self._models[SlippageType.LINEAR] = LinearSlippageModel(self.default_params)
        self._models[SlippageType.SQUARE_ROOT] = SquareRootSlippageModel(self.default_params)
        self._models[SlippageType.LOGARITHMIC] = LogarithmicSlippageModel(self.default_params)
        self._models[SlippageType.MARKET_IMPACT] = MarketImpactSlippageModel(self.default_params)
    
    def register_model(self, slippage_type: SlippageType, model: BaseSlippageModel) -> None:
        """注册滑点模型"""
        self._models[slippage_type] = model
    
    def calculate_slippage(
        self,
        order: Order,
        market_data: MarketData,
        model_type: SlippageType = SlippageType.LINEAR,
        execution_time: Optional[datetime] = None
    ) -> SlippageResult:
        """
        计算滑点
        
        Args:
            order: 订单信息
            market_data: 市场数据
            model_type: 滑点模型类型
            execution_time: 执行时间
            
        Returns:
            SlippageResult: 滑点结果
        """
        if model_type not in self._models:
            raise ValueError(f"Unsupported slippage model: {model_type}")
        
        model = self._models[model_type]
        return model.calculate_slippage(order, market_data, execution_time)
    
    def get_model(self, model_type: SlippageType) -> Optional[BaseSlippageModel]:
        """获取滑点模型"""
        return self._models.get(model_type)
    
    def update_params(self, model_type: SlippageType, params: SlippageParams) -> None:
        """更新模型参数"""
        if model_type in self._models:
            self._models[model_type].params = params
    
    def compare_models(
        self,
        order: Order,
        market_data: MarketData,
        execution_time: Optional[datetime] = None
    ) -> Dict[SlippageType, SlippageResult]:
        """
        比较不同滑点模型的结果
        
        Args:
            order: 订单信息
            market_data: 市场数据
            execution_time: 执行时间
            
        Returns:
            Dict[SlippageType, SlippageResult]: 各模型的滑点结果
        """
        results = {}
        for model_type in self._models:
            try:
                result = self.calculate_slippage(order, market_data, model_type, execution_time)
                results[model_type] = result
            except Exception as e:
                print(f"Error calculating slippage for {model_type}: {e}")
        
        return results
    
    def get_statistics(self, results: List[SlippageResult]) -> Dict[str, Any]:
        """
        获取滑点统计信息
        
        Args:
            results: 滑点结果列表
            
        Returns:
            Dict[str, Any]: 统计信息
        """
        if not results:
            return {}
        
        slippage_rates = [result.slippage_rate for result in results]
        slippage_costs = [float(result.slippage_cost) for result in results]
        
        return {
            "count": len(results),
            "avg_slippage_rate": sum(slippage_rates) / len(slippage_rates),
            "min_slippage_rate": min(slippage_rates),
            "max_slippage_rate": max(slippage_rates),
            "total_slippage_cost": sum(slippage_costs),
            "avg_slippage_cost": sum(slippage_costs) / len(slippage_costs),
            "model_distribution": {
                model_type.value: len([r for r in results if r.model_type == model_type])
                for model_type in SlippageType
            }
        }